/*
 * Copyright (c) 2024, Phytium Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <systemd/sd-journal.h>

// #include <ipmiblob/crc.hpp>
#include <ipmid/api-types.hpp>
#include <ipmid/api.hpp>
#include <ipmid/handler.hpp>
#include <ipmid/utils.hpp>
#include <phosphor-logging/log.hpp>

namespace oem
{
constexpr uint8_t cmdBinReceive = 0x52;
} // namespace oem

using namespace phosphor::logging;

static TSBLib::TSB tsb;

std::array<uint8_t, 4096> resPayload;
uint16_t sentDataSize = 0;
uint32_t resPayloadSize = 0;

// poweroff
static void powerOff()
{
    const char* chassisStatePath = "/xyz/openbmc_project/state/chassis0";
    const char* chassisStateIntf = "xyz.openbmc_project.State.Chassis";

    try
    {
        sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());

        ipmi::setDbusProperty(
            bus, ipmi::getService(bus, chassisStateIntf, chassisStatePath),
            chassisStatePath, chassisStateIntf, "RequestedPowerTransition",
            "xyz.openbmc_project.State.Chassis.Transition.Off");
    }
    catch (const std::runtime_error& e)
    {
        log<level::ERR>(e.what());
    }
}

static constexpr uint16_t TPM2_ST_NO_SESSIONS = 0x8001;
static constexpr uint32_t TPM2_CC_PCR_Extend  = 0x00000182;
static constexpr uint16_t TPM2_ALG_SHA256     = 0x000B;
static constexpr uint32_t PCR_INDEX_0         = 0x00000000; // PCR0

int sendHashToTpm(const std::string& hash, TSBLib::TSB& tsb)
{
    if (hash.size() != 32) {
        throw std::runtime_error("Hash size must be 32 bytes for SHA256");
    }

    // 构建 TPM 命令缓冲区
    // 计算命令长度：Header(10 bytes) + handle(4) + authSize(4,0值) + digestCount(4) + alg(2) + hash(32)
    // Header: tag(2) + length(4) + commandCode(4) = 10 bytes
    // handle: 4 bytes
    // authSize: 本例中无授权，会为0，4 bytes
    // digestCount: 4 bytes (1个digest)
    // alg: 2 bytes
    // hash: 32 bytes
    uint32_t commandSize = 10 + 4 + 4 + 4 + 2 + 32;
    std::vector<uint8_t> command(commandSize, 0);

    size_t offset = 0;

    // TPM Header
    // tag (2 bytes)
    uint16_t tag = TPM2_ST_NO_SESSIONS;
    memcpy(command.data() + offset, &tag, sizeof(tag));
    offset += sizeof(tag);

    // length (4 bytes)
    uint32_t length = commandSize;
    uint32_t length_be = htonl(length); // 确保端序
    memcpy(command.data() + offset, &length_be, sizeof(length_be));
    offset += sizeof(length_be);

    // commandCode (4 bytes)
    uint32_t commandCode = TPM2_CC_PCR_Extend;
    uint32_t commandCode_be = htonl(commandCode);
    memcpy(command.data() + offset, &commandCode_be, sizeof(commandCode_be));
    offset += sizeof(commandCode_be);

    // PCR handle (4 bytes)
    uint32_t pcrHandle = PCR_INDEX_0;
    uint32_t pcrHandle_be = htonl(pcrHandle);
    memcpy(command.data() + offset, &pcrHandle_be, sizeof(pcrHandle_be));
    offset += sizeof(pcrHandle_be);

    // authSize (4 bytes, 无授权则为0)
    uint32_t authSize = 0;
    // 不需要转换字节序为大端，因为是0
    memcpy(command.data() + offset, &authSize, sizeof(authSize));
    offset += sizeof(authSize);

    // digestCount (4 bytes), 本例中为1
    uint32_t digestCount = 1;
    uint32_t digestCount_be = htonl(digestCount);
    memcpy(command.data() + offset, &digestCount_be, sizeof(digestCount_be));
    offset += sizeof(digestCount_be);

    // hashAlg (2 bytes)
    uint16_t hashAlg = TPM2_ALG_SHA256;
    uint16_t hashAlg_be = htons(hashAlg);
    memcpy(command.data() + offset, &hashAlg_be, sizeof(hashAlg_be));
    offset += sizeof(hashAlg_be);

    // hash data (32 bytes)
    memcpy(command.data() + offset, hash.data(), 32);
    offset += 32;

    // 此时 command 是一个打包好的PCR_Extend命令缓冲区
    // 调用 tsb.ForwardMemory 将此 TPM 命令发送给 swtpm
    // 假设 swtpm 会返回 TPCM_RESULT，用于表示PCR校验结果
    
    resPayloadSize = resPayload.size();
    int result = tsb.ForwardMemory(command.data(), command.size(),
                                   resPayload.data(), resPayloadSize);

    return result;
}


ipmi::RspType<std::vector<uint8_t>> ipmiBinReceiveData(
    const std::vector<uint8_t>& reqData)
{
    static std::vector<uint8_t> reqPayload;
    reqPayload.reserve(256);
    static uint16_t index = 0;
    // Determine if the data is valid
    size_t reqDataSize = reqData.size();

    auto headLen = sizeof(struct oem::TcmIpmiHead);
    if (reqDataSize <= headLen)
    {
        log<level::ERR>("Not a valid data");
        return ipmi::responseInvalidFieldRequest();
    }

    size_t reqBodyLen = reqDataSize - headLen;
    std::vector<uint8_t> bytes(reqBodyLen);

    struct oem::TcmIpmiHead reqHead;
    std::memcpy(&reqHead, reqData.data(), sizeof(reqHead));
    // The first package
    if (!reqHead.packageIndex)
    {
        index = 0;
        reqPayload.clear();
    }

    // The package needs to be sent in order.
    if ((index++) != reqHead.packageIndex)
    {
        index = 0;
        reqPayload.clear();
        log<level::ERR>("Package index error, please resend.");
        return ipmi::responseInvalidFieldRequest();
    }

    // Calculate CRC verification
    std::memcpy(bytes.data(), &reqData[headLen], reqBodyLen);

    if (reqHead.crc16 != ipmiblob::generateCrc(bytes))
    {
        log<level::ERR>("CRC16 verification error.");
        return ipmi::responseInvalidFieldRequest();
    }

    // Copy payload to reqPayload
    reqPayload.insert(reqPayload.end(), bytes.begin(), bytes.end());

    // When receiving the last packet, write the payload of all packets to TCM
    if (reqHead.packageIndex == reqHead.packageTotal - 1)
    {
        std::string hash = tsb.ComputeSHA256(reqPayload.data(), reqPayload.size());

        int result = sendHashToTpm(hash, tsb);

        reqPayload.clear();
        sentDataSize = 0;
        if (!handleRc(result))
        {
            resPayloadSize = 0;
            log<level::ERR>("Meaure BIOS error.", entry("RESULT=%d", result));
            return ipmi::responseResponseError();
        }
    }
    return ipmi::responseSuccess();
}

static void register_fru_functions() __attribute__((constructor));
static void register_fru_functions()
{
    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnOemSix,
                          oem::cmdBinReceive, ipmi::Privilege::User,
                          ipmiBinReceiveData);
    return;
}
